#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "matrix.h"

/*-------------- struct matrix *new_matrix() --------------
 Inputs:  int rows
 int cols 
 Returns: 
 
 Once allocated, access the matrix as follows:
 m->m[r][c]=something;
 if (m->lastcol)... 
 */
struct matrix *new_matrix(int rows, int cols) {
	double **tmp;
	int i;
	struct matrix *m;
	
	tmp = (double **)malloc(rows * sizeof(double *));
	for (i=0;i<rows;i++) {
		tmp[i]=(double *)malloc(cols * sizeof(double));
    }
	
	m=(struct matrix *)malloc(sizeof(struct matrix));
	m->m=tmp;
	m->rows = rows;
	m->cols = cols;
	m->lastcol = 0;
	
	return m;
}


/*-------------- void free_matrix() --------------
 Inputs:  struct matrix *m 
 Returns: 
 
 1. free individual rows
 2. free array holding row pointers
 3. free actual matrix
 */
void free_matrix(struct matrix *m) {
	
	int i;
	for (i=0;i<m->rows;i++) {
		free(m->m[i]);
    }
	free(m->m);
	free(m);
}


/*======== void grow_matrix() ==========
 Inputs:  struct matrix *m
 int newcols 
 Returns: 
 
 Reallocates the memory for m->m such that it now has
 newcols number of collumns
 ====================*/
void grow_matrix(struct matrix *m, int newcols) {
	int i;
	for (i=0;i<m->rows;i++) {
		m->m[i] = realloc(m->m[i],newcols*sizeof(double));
    }
	m->cols = newcols;
}


/*-------------- void print_matrix() --------------
 Inputs:  struct matrix *m 
 Returns: 
 
 print the matrix
 */
void print_matrix(struct matrix *m) {
	int i, j;
	i = j = 0;
	while (i < m->rows){
		j = 0;
		while (j < m->cols){
			printf("%f ", m->m[i][j]);
			j++;
		}
		printf("\n");
		i++;
	}
}


/*-------------- void ident() --------------
 Inputs:  struct matrix *m <-- assumes m is a square matrix
 Returns: 
 
 turns m in to an identity matrix
 */
void ident(struct matrix *m) {
	int i, j;
	i = j = 0;
	while (i < m->rows){
		j = 0;
		while (j < m->cols){
			if (i == j)
				m->m[i][j] = 1;
			else
				m->m[i][j] = 0;
			j++;
		}
		i++;
	}
}


/*-------------- void scalar_mult() --------------
 Inputs:  double x
 struct matrix *m 
 Returns: 
 
 multiply each element of m by x
 */
void scalar_mult(double x, struct matrix *m) {
	int i, j;
	i = j = 0;
	while (i < m->rows){
		j = 0;
		while (j < m->cols){
			m->m[i][j] *= x;
			j++;
		}
		i++;
	}
}


/*-------------- void matrix_mult() --------------
 Inputs:  struct matrix *a
 struct matrix *b 
 Returns: 
 
 a*b -> b
 */
void matrix_mult(struct matrix *a, struct matrix *b) {
	int r, c;
	struct matrix * tmp;
	
	tmp = new_matrix(4, 1);
	
	for (c=0; c < b->cols; c++)  {
		for (r=0; r < 4; r++) 
			tmp->m[r][0] = b->m[r][c];
		
		for (r=0; r < 4; r++) 
			b->m[r][c] =  a->m[r][0] * tmp->m[0][0] + 
			a->m[r][1] * tmp->m[1][0] +
			a->m[r][2] * tmp->m[2][0] +
			a->m[r][3] * tmp->m[3][0];
	}
	free_matrix(tmp);
}



/*-------------- void copy_matrix() --------------
 Inputs:  struct matrix *a
 struct matrix *b 
 Returns: 
 
 copy matrix a to matrix b
 */
void copy_matrix(struct matrix *a, struct matrix *b) {
	int i, j;
	i = j = 0;
	while (i < a->rows){
		j = 0;
		while (j < a->cols){
			b->m[i][j] = a->m[i][j];
			j++;
		}
		i++;
	}
}

/*======== struct matrix * make_translate() ==========
 Inputs:  double x
 double y
 double z 
 Returns: The translation matrix created using x, y and z 
 as the translation offsets.
 ====================*/
struct matrix * make_translate(double x, double y, double z) {
	struct matrix * iseverywhere;
	iseverywhere = new_matrix(4, 4);
	ident(iseverywhere);
	iseverywhere->m[0][3] = x;
	iseverywhere->m[1][3] = y;
	iseverywhere->m[2][3] = z;
	return iseverywhere;
}

/*======== struct matrix * make_scale() ==========
 Inputs:  int x
 int y
 int z 
 Returns: The translation matrix creates using x, y and z
 as the scale factors
 ====================*/
struct matrix * make_scale(double x, double y, double z) {
	struct matrix * iseverywhere;
	iseverywhere = new_matrix(4, 4);
	ident(iseverywhere);
	iseverywhere->m[0][0] = x;
	iseverywhere->m[1][1] = y;
	iseverywhere->m[2][2] = z;
	return iseverywhere;
}

/*======== struct matrix * make_rotX() ==========
 Inputs:  double theta
 
 Returns: The rotation matrix created using theta as the 
 angle of rotation and X as the axis of rotation.
 ====================*/
struct matrix * make_rotX(double theta) {
	theta *= M_PI / 180;
	struct matrix * iseverywhere;
	iseverywhere = new_matrix(4, 4);
	ident(iseverywhere);
	iseverywhere->m[2][2] = cos(theta);
	iseverywhere->m[1][2] = -1 * sin(theta);
	iseverywhere->m[2][1] = sin(theta);
	iseverywhere->m[1][1] = cos(theta);
	return iseverywhere;
}

/*======== struct matrix * make_rotY() ==========
 Inputs:  double theta
 char c 
 Returns: The rotation matrix created using theta as the 
 angle of rotation and Y as the axis of rotation.
 ====================*/
struct matrix * make_rotY(double theta) {
	theta *= M_PI / 180;
	struct matrix * iseverywhere;
	iseverywhere = new_matrix(4, 4);
	ident(iseverywhere);
	iseverywhere->m[0][0] = cos(theta);
	iseverywhere->m[2][0] = -1 * sin(theta);
	iseverywhere->m[0][2] = sin(theta);
	iseverywhere->m[2][2] = cos(theta);
	return iseverywhere;
}

/*======== struct matrix * make_rotZ() ==========
 Inputs:  double theta
 char c 
 Returns: The rotation matrix created using theta as the 
 angle of rotation and Z as the axis of rotation.
 ====================*/
struct matrix * make_rotZ(double theta) {
	theta *= M_PI / 180;
	struct matrix * iseverywhere;
	iseverywhere = new_matrix(4, 4);
	ident(iseverywhere);
	iseverywhere->m[0][0] = cos(theta);
	iseverywhere->m[0][1] = -1 * sin(theta);
	iseverywhere->m[1][0] = sin(theta);
	iseverywhere->m[1][1] = cos(theta);
	return iseverywhere;
}
